package ar.com.gep.soccer.facade;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import ar.com.cdt.framework.business.bo.crud.GenericCRUDBO;
import ar.com.cdt.framework.business.exception.CDTFrameworkBusinessException;
import ar.com.cdt.framework.core.exception.CDTFrameworkException;
import ar.com.cdt.framework.tools.dozer.mappers.DozerMapper;
import ar.com.gep.soccer.entities.Edition;
import ar.com.gep.soccer.entities.Game;
import ar.com.gep.soccer.entities.League;
import ar.com.gep.soccer.entities.Position;
import ar.com.gep.soccer.entities.Round;
import ar.com.gep.soccer.entities.Season;
import ar.com.gep.soccer.entities.SeasonItem;
import ar.com.gep.soccer.entities.Team;
import ar.com.gep.soccer.exception.EditionBusinessException;
import ar.com.gep.soccer.exception.LeagueBusinessException;
import ar.com.gep.soccer.exception.MatchBusinessException;
import ar.com.gep.soccer.exception.RoundBusinessException;
import ar.com.gep.soccer.exception.SeasonBusinessException;
import ar.com.gep.soccer.exception.TeamBusinessException;
import ar.com.gep.soccer.services.dto.EditionDTO;
import ar.com.gep.soccer.services.dto.GameDTO;
import ar.com.gep.soccer.services.dto.LeagueDTO;
import ar.com.gep.soccer.services.dto.PositionDTO;
import ar.com.gep.soccer.services.dto.RoundDTO;
import ar.com.gep.soccer.services.dto.SeasonDTO;
import ar.com.gep.soccer.services.dto.SeasonItemDTO;
import ar.com.gep.soccer.services.dto.TeamDTO;
import ar.com.gep.soccer.services.dto.WeekInfoRowDTO;
import ar.com.gep.soccer.services.dto.WeekInfoSeasonDTO;
import ar.com.gep.soccer.utils.engine.Engine;
import ar.com.gep.soccer.utils.generator.FixtureGenerator;

@Component
public class TournamentFacadeImpl implements TournamentFacade {

	@Autowired
	@Qualifier("game.bo")
	private GenericCRUDBO<Game> gameBO;

	@Autowired
	@Qualifier(value = "round.bo")
	private GenericCRUDBO<Round> roundBO;

	@Autowired
	@Qualifier(value = "edition.bo")
	private GenericCRUDBO<Edition> editionBO;

	@Autowired
	@Qualifier(value = "league.bo")
	private GenericCRUDBO<League> leagueBO;

	@Autowired
	@Qualifier(value = "season.bo")
	private GenericCRUDBO<Season> seasonBO;

	@Autowired
	@Qualifier(value = "game.dozer")
	private DozerMapper<GameDTO, Game> gameMapper;

	@Autowired
	@Qualifier(value = "round.dozer")
	private DozerMapper<RoundDTO, Round> roundMapper;

	@Autowired
	@Qualifier(value = "edition.dozer")
	private DozerMapper<EditionDTO, Edition> editionMapper;

	@Autowired
	@Qualifier(value = "league.dozer")
	private DozerMapper<LeagueDTO, League> leagueMapper;

	@Autowired
	@Qualifier(value = "team.dozer")
	private DozerMapper<TeamDTO, Team> teamMapper;

	@Autowired
	@Qualifier(value = "season.dozer")
	private DozerMapper<SeasonDTO, Season> seasonMapper;
	
	@Autowired
	@Qualifier(value = "position.dozer")
	private DozerMapper<PositionDTO, Position> positionMapper;	

	/*
	 * (non-Javadoc)
	 * 
	 * @see ar.com.gep.soccer.facade.TournamentFacade#getMatch(java.lang.Long)
	 */
	@Override
	public GameDTO getMatch(final Long id) throws MatchBusinessException {
		GameDTO matchDTO;
		Game match;
		try {
			match = gameBO.getById(id);
			if (match == null) {
				throw new MatchBusinessException("Error al recuperar un partido con id " + id);
			}
			matchDTO = gameMapper.mapToDTO(match);
		} catch (CDTFrameworkException exception) {
			throw new MatchBusinessException("Error al recuperar un equipo con id " + id, exception);
		}
		return matchDTO;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * ar.com.gep.soccer.facade.TournamentFacade#saveMatch(ar.com.gep.soccer
	 * .services.dto.MatchDTO)
	 */
	@Override
	public GameDTO saveMatch(final GameDTO dto) throws TeamBusinessException {
		Game match = null;
		GameDTO matchDTO = null;
		try {
			match = gameBO.create(gameMapper.mapToEntity(dto));
			matchDTO = gameMapper.mapToDTO(match);
		} catch (CDTFrameworkBusinessException e) {
			throw new TeamBusinessException("No se pudo crear el partido", e);
		}
		return matchDTO;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * ar.com.gep.soccer.facade.TournamentFacade#updateMatch(ar.com.gep.soccer
	 * .services.dto.MatchDTO)
	 */
	@Override
	public GameDTO updateMatch(final GameDTO dto) throws TeamBusinessException {
		Game match = null;
		GameDTO matchDTO = null;
		try {
			match = gameBO.update(gameMapper.mapToEntity(dto));
			matchDTO = gameMapper.mapToDTO(match);
		} catch (CDTFrameworkBusinessException e) {
			throw new TeamBusinessException("No se pudo editar el partido", e);
		}
		return matchDTO;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * ar.com.gep.soccer.facade.TournamentFacade#saveRound(ar.com.gep.soccer
	 * .services.dto.RoundDTO)
	 */
	@Override
	public RoundDTO saveRound(final RoundDTO rounds) throws RoundBusinessException {
		Round round;
		RoundDTO dto = null;

		try {
			round = roundBO.create(roundMapper.mapToEntity(rounds));
			dto = roundMapper.mapToDTO(round);

		} catch (CDTFrameworkBusinessException e) {
			throw new RoundBusinessException("Error al crear una fecha", e);
		}
		return dto;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ar.com.gep.soccer.facade.TournamentFacade#getRound(java.lang.Long)
	 */
	@Override
	public RoundDTO getRound(final Long id) throws RoundBusinessException {
		RoundDTO dto = null;
		Round round;

		try {
			round = roundBO.getById(id);
			dto = roundMapper.mapToDTO(round);
		} catch (CDTFrameworkBusinessException e) {
			throw new RoundBusinessException("Error al recuperar una fecha", e);
		}
		return dto;
	}

	/**
	 * Devuelve un EditionDTO dado un id
	 * 
	 * @param id
	 * @return
	 * @throws CDTFrameworkBusinessException
	 */
	private EditionDTO getEditionDTO(final Long id, String mapping) throws CDTFrameworkBusinessException {
		Edition edition;
		EditionDTO editionDTO;
		edition = editionBO.getById(id);
		editionDTO = editionMapper.mapToDTO(edition, mapping);
		return editionDTO;
	}

	private EditionDTO getEditionDTO(Long id) throws CDTFrameworkBusinessException {
		return getEditionDTO(id, null);
	}

	@Override
	public EditionDTO getEdition(final Long id) throws EditionBusinessException {
		EditionDTO dto = null;
		try {
			dto = getEditionDTO(id);
		} catch (CDTFrameworkBusinessException e) {
			throw new EditionBusinessException("Error al recuperar una edicion", e);
		}
		return dto;
	}

	@Override
	public EditionDTO saveEdition(EditionDTO dto) throws EditionBusinessException {
		Edition edition;

		try {
			edition = editionMapper.mapToEntity(dto);
			edition.setTotalOfRounds(dto.getRounds() != null ? dto.getRounds().size() : 0);
			edition = editionBO.create(edition);
			dto = editionMapper.mapToDTO(edition);
		} catch (CDTFrameworkBusinessException e) {
			throw new EditionBusinessException("error en grabar una edicion", e);
		}
		return dto;
	}

	@Override
	public LeagueDTO getLeague(final Long id) throws LeagueBusinessException {
		LeagueDTO dto = null;
		League edition;

		try {
			edition = leagueBO.getById(id);
			dto = leagueMapper.mapToDTO(edition);
		} catch (CDTFrameworkBusinessException e) {
			throw new LeagueBusinessException("Error al recuperar una liga", e);
		}
		return dto;
	}

	@Override
	public LeagueDTO saveLeague(LeagueDTO dto) throws LeagueBusinessException {
		League league;

		try {
			league = leagueMapper.mapToEntity(dto);
			league = leagueBO.create(league);
			dto = leagueMapper.mapToDTO(league);
		} catch (CDTFrameworkBusinessException e) {
			throw new LeagueBusinessException("error en grabar una liga", e);
		}
		return dto;
	}

	/**
	 * Genera las posiciones iniciales
	 * 
	 * @param teamsDTO
	 * @return
	 */
	private List<PositionDTO> generatePositions(List<TeamDTO> teamsDTO) {
		List<PositionDTO> map = new ArrayList<PositionDTO>();
		for (TeamDTO teamDTO : teamsDTO) {
			map.add(new PositionDTO(teamDTO, 0, 0, 0, 0, 0));
		}
		return map;
	}

	/**
	 * 
	 * @param teamsDTO
	 * @param complete
	 * @param leagueDTO
	 * @return
	 */
	private EditionDTO generateEditionWithRounds(final List<TeamDTO> teamsDTO, final Boolean complete, LeagueDTO leagueDTO) {
		EditionDTO edition;
		List<RoundDTO> generateGamees;
		edition = new EditionDTO();
		edition.setNumber(leagueDTO.getCurrentEdition() + 1);
		generateGamees = FixtureGenerator.generateGamees(teamsDTO, complete);
		edition.setRounds(generateGamees);
		edition.setPositions(generatePositions(teamsDTO));
		return edition;
	}

	@Override
	public EditionDTO createEditionLeague(final Long id, final List<TeamDTO> teamsDTO, final Boolean complete) throws LeagueBusinessException {
		LeagueDTO leagueDTO;
		EditionDTO edition;
		try {
			leagueDTO = leagueMapper.mapToDTO(leagueBO.getById(id), "createEditionLeague");

			edition = saveEdition(generateEditionWithRounds(teamsDTO, complete, leagueDTO));

			leagueDTO.getEditions().add(edition);

			updateLeague(leagueDTO);

			return edition;

		} catch (CDTFrameworkBusinessException e) {
			throw new LeagueBusinessException("error al crear una edicion para la liga", e);
		}
	}

	@Override
	public LeagueDTO updateLeague(final LeagueDTO leagueDTO) throws LeagueBusinessException {
		League league;
		league = leagueMapper.mapToEntity(leagueDTO);
		try {
			league = leagueBO.update(league);
			return leagueMapper.mapToDTO(league);
		} catch (CDTFrameworkBusinessException e) {
			throw new LeagueBusinessException("Error al actualizar la liga", e);
		}
	}

	@Override
	public Boolean canPlayEdition(final Long id) throws EditionBusinessException {
		Boolean result = Boolean.FALSE;
		Edition edition;
		try {
			edition = editionBO.getById(id);
			if (edition.getNumberOfRounds() < edition.getTotalOfRounds()) {
				result = Boolean.TRUE;
			}
		} catch (CDTFrameworkBusinessException e) {
			throw new EditionBusinessException("Error al verificar si se puede jugar esta edicion", e);
		}
		return result;

	}

	/**
	 * Se juega el partido
	 * 
	 * @param gameDTO
	 * @throws TeamBusinessException
	 */
	private void playGame(GameDTO gameDTO) throws TeamBusinessException {
		Engine.getEngine().playGame(gameDTO, gameDTO.getHomeSkills(), gameDTO.getAwaySkill());
		gameDTO.setState(Boolean.TRUE);
		updateMatch(gameDTO);
	}

	/**
	 * Realiza el calculo de una fecha
	 * 
	 * @param roundDTO
	 * @throws TeamBusinessException
	 */
	private void playRound(RoundDTO roundDTO) throws TeamBusinessException {
		for (GameDTO gameDTO : roundDTO.getGames()) {
			playGame(gameDTO);
		}
	}

	/**
	 * Actualiza una edicion
	 * 
	 * @param editionDTO
	 * @return
	 * @throws EditionBusinessException
	 */
	private EditionDTO updateEdition(EditionDTO editionDTO) throws EditionBusinessException {
		Edition edition;
		try {
			edition = editionMapper.mapToEntity(editionDTO);
			edition = editionBO.update(edition);
			editionDTO = editionMapper.mapToDTO(edition);
			return editionDTO;
		} catch (CDTFrameworkBusinessException e) {
			throw new EditionBusinessException("Error al actualizar una edicion", e);
		}
	}

	@Override
	public RoundDTO playEditionWeek(final Long id) throws EditionBusinessException {
		EditionDTO editionDTO;
		RoundDTO roundDTO;
		try {
			if (canPlayEdition(id)) {
				editionDTO = getEditionDTO(id, "getCompleteEdition");
				roundDTO = editionDTO.getPlayRound();
				playRound(roundDTO);
				editionDTO.calculatePositions();
				editionDTO.incrementRound();
				updateEdition(editionDTO);
				return roundDTO;
			}
		} catch (CDTFrameworkBusinessException e) {
			throw new EditionBusinessException("Error al jugar un partido", e);
		}
		return null;
	}

	@Override
	public RoundDTO playLeagueWeek(final Long id) throws LeagueBusinessException {
		League league;
		try {
			league = leagueBO.getById(id);
			return playEditionWeek(league.getEditions().get(league.getCurrentEdition()).getId());
		} catch (CDTFrameworkBusinessException e) {
			throw new LeagueBusinessException("Error al jugar una semana para la liga", e);
		}
	}

	@Override
	public SeasonDTO getSeasonById(final Long id) throws SeasonBusinessException {
		Season season;
		SeasonDTO seasonDTO;
		try {
			season = seasonBO.getById(id);
			seasonDTO = seasonMapper.mapToDTO(season);
			return seasonDTO;
		} catch (CDTFrameworkBusinessException e) {
			throw new SeasonBusinessException("Error al recuperar una temporada", e);
		}
	}

	@Override
	public int getTotalRoundLastEdition(final Long id) throws LeagueBusinessException {
		League league;
		Edition edition;
		try {
			league = leagueBO.getById(id);
			edition = league.getEditions().get(league.getCurrentEdition());
			return edition.getTotalOfRounds();
		} catch (CDTFrameworkBusinessException e) {
			throw new LeagueBusinessException("error al recuperar el total de la ediciona ctual para la liga", e);
		}
	}

	/**
	 * Calcula el numero total de semanas que necesita la temporada
	 * 
	 * @param items
	 * @return
	 */
	private int getTotalWeeks(List<SeasonItemDTO> items) {
		int max = 0;
		int diff;
		for (SeasonItemDTO seasonItemDTO : items) {
			diff = seasonItemDTO.getDateEnd() - seasonItemDTO.getDateStart();
			if (max < diff) {
				max = diff;
			}
		}
		return max;
	}

	@Override
	public SeasonDTO saveSeason(SeasonDTO seasonDTO) throws SeasonBusinessException {
		Season season;
		try {
			seasonDTO.setTotalWeek(getTotalWeeks(seasonDTO.getItems()));
			season = seasonMapper.mapToEntity(seasonDTO);
			season = seasonBO.create(season);
			return seasonMapper.mapToDTO(season);
		} catch (CDTFrameworkBusinessException e) {
			throw new SeasonBusinessException("Error al crear una temporada", e);
		}
	}

	@Override
	public SeasonDTO updateSeason(SeasonDTO seasonDTO) throws SeasonBusinessException {
		Season season;
		try {
			season = seasonMapper.mapToEntity(seasonDTO);
			season = seasonBO.update(season);
			return seasonMapper.mapToDTO(season);
		} catch (CDTFrameworkBusinessException e) {
			throw new SeasonBusinessException("Error al actualizar una temporada", e);
		}
	}

	@Override
	public SeasonDTO playSeasonWeek(final Long id) throws SeasonBusinessException {
		SeasonDTO seasonDTO;
		Season season;
		try {
			season = seasonBO.getById(id);
			for (SeasonItem seasonItem : season.getItems()) {
				if (seasonItem.getDateStart() <= season.getWeek() && season.getWeek() <= seasonItem.getDateEnd()) {
					playLeagueWeek(seasonItem.getLeague().getId());
				}
			}
			seasonDTO = seasonMapper.mapToDTO(season);
			seasonDTO.setWeek(seasonDTO.getWeek() + 1);
			return updateSeason(seasonDTO);
		} catch (CDTFrameworkBusinessException e) {
			throw new SeasonBusinessException("Error al jugar una semana de la temp.", e);
		}
	}

	private WeekInfoRowDTO generateWeekInfoRow(SeasonItem item, Integer week) {
		WeekInfoRowDTO dto = new WeekInfoRowDTO();

		League league = item.getLeague();
		dto.setLeague(league.getName());
		
		Edition edition = league.getEditions().get(league.getCurrentEdition());

		Integer editionNumber = edition.getNumber();
		dto.setEdition(editionNumber.toString());

		Round round = edition.getRounds().get(week);
		dto.setRound(roundMapper.mapToDTO(round));
		
		dto.setPositions(positionMapper.mapToDTO(edition.getPositions()));

		return dto;
	}

	private List<WeekInfoRowDTO> getWeekInfoRows(final Season season, final Integer week) {
		List<WeekInfoRowDTO> rows = new ArrayList<WeekInfoRowDTO>();
		for (SeasonItem item : season.getItems()) {
			rows.add(generateWeekInfoRow(item, week));
		}
		return rows;
	}

	@Override
	public WeekInfoSeasonDTO getWeekInfo(final Long id, final Integer week) throws SeasonBusinessException {
		WeekInfoSeasonDTO dto = new WeekInfoSeasonDTO();
		Season season;
		try {
			season = seasonBO.getById(id);
			dto.setRows(getWeekInfoRows(season, week));
		} catch (CDTFrameworkBusinessException e) {
			throw new SeasonBusinessException("Error al recuperar la info de semana", e);
		}
		return dto;
	}

}
